WICONF.initGrid = {
    tableHBorderW: 1,// .表格设置的宽度与实际显示的宽度的差值，可能因为边框与单元格合并，实际宽度产生误差，此值需要随 css 调整
    defaultColWidth: 80// 未定义宽度的列的最小宽度
};
/**
 * @widoc $.fn.initGrid
 * @namespace comp
 * @des 初始化数据列表表格
 * 组件配置，在使用组件前调用：
 <pre class="des-conf"> $.setWiConf('initGrid',{
    tableHBorderW: 1,// table 设置会因为边框与单元格合并，实际宽度产生误差，此值需要随 css 调整，只在 opts.staticSize = false 时生效
    defaultColWidth: 80// 未定义宽度的列的最小宽度，只在 opts.staticSize = false 时生效
 });</pre>
 * @type function
 * @demo initGrid/demo 分页列表（前台+后台），后台返回数据为 {success:true,msg:'错误提示',data:{totalcount:100,retlist:[]}}
 * @demo initGrid/demo_nopage 不分页列表（前台+后台），后台返回数据为 {success:true,msg:'错误提示',data:[]}
 * @demo initGrid/demo_merge 合并单元格
 * @param {object} opts 配置参数
 * @param {number=} opts.defaultColWidth 未定义宽度的列的最小宽度，默认为 80。只在 opts.staticSize = false 时生效。
 * @param {number=} opts.tableHBorderW 表格设置的宽度与实际显示的宽度的差值（与 css 相关），默认为 1。只在 opts.staticSize = false 时生效。
 * @param {boolean=} opts.staticSize 组件是否是静态尺寸，设为 false 将根据容器动态调整尺寸，性能降低，默认为 true。
 * @param {Array} opts.cols 列定义 [{field,...}]
 * @param {string=} opts.cols[i].field 该列对应的属性名
 * @param {string=} opts.cols[i].name 该列的唯一标识，不允许与其他列的 name 相同，默认与 field 相同，若未定义 field，则 name 必须。除 type='no'(组件内默认 name='__no')，type='chk'(组件内默认 name='__checked')时可省略 name。
 * @param {string=} opts.cols[i].title 该列显示的标题文本
 * @param {boolean=} opts.cols[i].showTip 该列鼠标悬停时是否显示完整的内容，默认为 false
 * @param {string=} opts.cols[i].type 该列显示的数据类型，默认为：'string'，可选值：'number','string','no'(序号列，唯一),'chk'(选择列，唯一)
 * @param {string=} opts.cols[i].cls 该列自定义 class 名。
 * @param {string=} opts.cols[i].width 该列宽度，需带单位 'px' 或 '%'。
 *  - opts.staticSize=false 时，% 将以 el 容器宽度作为计算基数。若所有列都定义了 width，表格宽度为所有列实际宽度之和，可能小于 el 宽，或出现滚动条。若希望表格撑满容器，请保留至少一列不定义 width
 *  - opts.staticSize=true 时，% 与 px 混用时，列宽会由表格自动分配，实际显示与定义可能不同。
 * @param {string=} opts.cols[i].align 对齐方式，可选值：'center','left','right'。不指定时，type='string'时为'left'，type='number'时为'right'，type='no'时为'center'，type='chk'时为'center'
 * @param {boolean=} opts.cols[i].sort 该列是否允许排序，默认 false TODO 功能待实现
 * @param {function=} opts.cols[i].render 该列的渲染函数 render(val,row,i,grid) val 单元格值，row 行数据，i 当前行索引（相对当前页），grid 接口对象；返回 dom 字符串
 * @param {function=} opts.cols[i].hrender 该列标题部分的渲染函数 hrender(name,title) 当前列的 name 及 title
 * @param {boolean=} opts.showHead 是否显示表头，默认为 true
 * @param {boolean=} opts.showno <span class="t_gray80">通过在 opts.cols 中添加列定义 {type:'no'} 实现</span>
 * @param {boolean=} opts.singleChk 若定义为 true，则只能进行单选。默认为 false。
 * @param {boolean|string=} opts.chkField opts.cols 中未添加 type='chk' 的列时，若将此属性定义为 string，则将以此字段作为标识选中状态的 field。若值为 true，或虽未定义，但 opts.singleChk=true，则该字段为 '__checked'。
 *  - 推荐的使用场景：单选，但不希望显示选择列。其他情况：<span class="t_gray80">请通过在 opts.cols 中添加列定义 {type:'chk'} 或 {type:'chk',field:'...'} 实现其他情况：设置了此属性，且未修改 opts.singleChk，则显示复选框。</span>
 * @param {object=} opts.filterEl 过滤文本框对象，后台分页时不支持过滤。
 * @param {object=} opts.filter 过滤功能配置，仅当定义了 filterEl 时生效。
 * @param {string|Array=} opts.filter.fields 参与过滤的列的属性名，对应 cols[i].field。
 * @param {boolean=} opts.filter.pySupport 是否支持拼音首字过滤，开启过滤时，默认为 true。
 * @param {string=} opts.filter.placeholder 过滤搜索框占位符(不支持IE8,9)
 * @param {boolean=} opts.filter.hide 是否隐藏不符合过滤条件的行，默认为 true，复选时此属性无效。
 * @param {boolean=} opts.prompt 无数据时是否显示提示，默认为 true。
 * @param {function=} opts.rowClser 行自定义class函数。function(row,i){...} 参数参考 opts.cols[i].render。
 * @param {object=} opts.callback 回调函数定义。
 * @param {object=} opts.callback.cell 单元格事件。
 <pre class="des-conf">cell:{
    ctrl:{// 对应 cols 中定义的 opts.cols[i].name
       // this 指向当前单元格 dom 对象
       // 参数参考 opts.cols[i].render，e 为触发事件的 event 对象
       click:function(e,val,row,i){...}
    }
 }</pre>
 * @param {object=} opts.callback.row 行事件。
 <pre class="des-conf">row:{
    // this 指向当前行 dom 对象，参数参考 opts.callback.cell
    click:function(e,row,i){...}
 }</pre>
 * @param {function=} opts.callback.afterShowData 显示完数据的回调函数。
 *          function(grid){...}
 * @param {function=} opts.callback.onCheckRow 选中行时的回调函数。
 *          function(row,i,el){...} 参数依次是：行数据，行索引，行对应的tr对象
 * @param {function=} opts.callback.onUnCheckRow 取消行选中时的回调函数。
 *          function(row,i,el){...} 参数依次是：行数据，行索引，行对应的tr对象
 * @param {Array=} opts.data 静态数据，优先级高于 opts.url。
 * @param {string=} opts.url 数据请求路径。
 * @param {object=} opts.args 请求参数的键值对，只在 opts.url 有意义时生效。
 * @param {boolean=} opts.showloading 向后台请求数据时的 showloading 配置，参考 $.request(opts) 中的 opts.showloading。
 * @param {boolean|object=} opts.page 分页配置，默认为 false，即不分页。设置为对象时，作为初始化分页条的参数，参考 $.fn.initPageBar()。
 * @return {object} obj 接口对象
 * @rtn {object} obj.el 获取当前 grid 对应的 el。
 * @rtn {function} obj.fResetSize() 初始化时可能由于元素隐藏或祖先元素隐藏，导致无法在容器变化时重算尺寸，此时可手动调用此方法触发进行尺寸重算。opts.staticSize 为 false 时生效。
 * @rtn {function} obj.fReset(cusOpts) 扩展初始化时的配置，重新请求数据（分页）。
 *   - {object} cusOpts 参考初始化参数。
 *   - 支持重置的属性：staticSize, cols, filter, prompt, rowClser, callback, data, url, args, showloading, page
 *   - 复杂属性中，会直接用新配置直接替换原有对象的属性：cols, filter, callback；会在原有对象基础上扩展的属性：page
 * @rtn {function} obj.resetData(cusOpts) <span class="t_gray80">请用 obj.fReset() 替代。</span>
 * @rtn {function} obj.getData() 返回整个表格的的数据列表 {Array}，后台分页且未过滤时，同 obj.getCurData() 的结果。
 * @rtn {function} obj.getCurData() 返回当前显示（分页时为当前页数据，过滤时为过滤后当前页数据）的数据列表 {Array}。
 * @rtn {function} obj.setCurData(data) 重置当前显示的数据列表，不会影响 obj.getData() 的结果。
 *   - {Array} data 新的列表数据。
 * @rtn {function} obj.setData(data) <span class="t_gray80">请用 obj.setCurData() 替代。</span>
 * @rtn {object} obj.findCol(key,name) 根据条件查找所有符合的 cols[i].key=val 的列定义对象集合，未找到则为 []
 * @rtn {function} obj.findRow(field,val) 根据条件查找所有符合的 data[i].field=val 的行对象集合，未找到则为 []
 * @rtn {function} obj.getCheckedIdx() 获取当前列表所有选中行索引，返回选中行索引数组，未找到则为 []
 * @rtn {function} obj.getCheckedRows() 获取当前列表所有选中行，返回选中行数据数组，未找到则为 []
 * @rtn {function} obj.checkRow(row,chk) 修改指定行的选中状态
 *   - {object|Array} row 要选中的行对应的数据，必须为当前 grid 中的数据对象，支持多条数据组成的数组
 *   - {boolean=} chk 设为 true 将 row 选中，false 将 row 取消选中，默认为 true
 * @rtn {function} obj.getPage() 返回当前的分页信息，返回值 {percount,totalpage,curpage,totalcount}
 * @rtn {function} obj.getPageApi() 返回当前分页接口 api
 * @rtn {function} obj.getPageObj() <span class="t_gray80">请用 obj.getPageApi() 替代。</span>
 * @rtn {function} obj.merge(merges) 合并单元格
 *   - merges {Array} 要合并的单元格信息
 *       merges[i].rowI {int} 要合并的单元格在当前页的索引
 *       merges[i].name {string} 要合并的单元格所在列对应的 name
 *       merges[i].rowspan {int} 合并几行
 *       merges[i].colspan {int} 合并几列
 * @rtn {function} obj.destroy() <span class="t_red">销毁 grid。暂无应用场景，已取消支持。</span>
 * @rtn {function} obj.getOpts() 获取当前列表配置（为扩展编辑表格等功能添加此接口）
 */
$.fn.initGrid = function(opts){
    var el = $(this),
        api,
        CONF = WICONF.initGrid;// 全局配置
    opts = $.extend({
        defaultColWidth: CONF.defaultColWidth,
        tableHBorderW: CONF.tableHBorderW,
        staticSize: true,// 组件是否是静态尺寸，设为 true 将不会监听尺寸变化，提高效率
        cols: [],
        showHead: true,
        showno: false,// TODO del 兼容旧版本，现通过在 opts.cols 中添加列定义 {type:'no'} 实现
        singleChk: false,
        chkField: false,// 推荐仅在单选且不显示选择列时使用
        //filterEl,
        filter: {},
        prompt: true,
        //rowClser:function(curdata,i),
        //callback:{},
        //data:[],
        //url:'',
        //args:{},
        showloading: false,
        page: false
    }, opts);
    var oPageOpts,
        sNoName,// 序号列 name
        sChkName, sChkField,// 选择列 name，选择列 field，fDrawDom() 时赋值
        aColsDef,// 列定义
        _cb,// 回调配置
        _data,// 全部数据
        _curData; // 记录当前页数据
    var oPagebarApi,
        _pageObj,// initPageBar 中绘制分页信息时回传的 oPagebarApi.page 更实时
        oFilterApi;
    var nElWidth,// 记录 el 容器尺寸，以便判断是否需要重新计算列宽
        aInitColsW = [], // 记录用户初始化时定义的列宽
        aAdaptCols = []; // 未指定宽度的列索引
    // 根据条件查找所有符合的 cols[i].key=val 的列
    // @return {Array} 所有符合的列定义对象集合，未找到则为 []
    var fFindCol = function(key, val){
        var idxArr = [];
        for(var i = 0; i < aColsDef.length; i++){
            var col = aColsDef[i];
            if(col[key] == val){
                idxArr.push(col);
            }
        }
        return idxArr;
    };
    // 根据条件查找所有符合的 data[i].field=val 的行
    // @return {Array} 所有符合的行对象集合，未找到则为 []
    var fFindRow = function(field, val){
        var rows = [];
        for(var i = 0; i < _data.length; i++){
            var row = _data[i];
            if(row[field] == val){
                rows.push(row);
            }
        }
        return rows;
    };
    // 改变指定行的选中状态
    // row {object|Array} 要选中的行对应的数据，必须为当前 grid 中的数据对象
    // chk {boolean=} true 时改为选中，false 改为不选中，默认为 true
    var fCheckRow = function(row, chk){
        if(!sChkField || !row) return;// 未定义可选中
        var bSingle = opts.singleChk,// 是否单选
            chkStats = chk !== false,// 默认为 true
            i;
        var applyRow = function(r, rChk){
            var oldChk = !!r[sChkField];// 修改前的选中状态
            if(oldChk != rChk){
                r[sChkField] = rChk;
                var i = _curData.indexOf(r);// 当前页中的索引
                if(i != -1){
                    var rowEl = el.find('tbody>tr[data-i="' + i + '"]');
                    rChk ? rowEl.addClass('trchk') : rowEl.removeClass('trchk');
                    rowEl.find('>td[data-name="' + sChkName + '"]>input').prop('checked', rChk);
                }
                if(_cb){
                    if(rChk){
                        _cb.onCheckRow && _cb.onCheckRow(r, i, rowEl, api);// 选中回调
                    }
                    else{
                        _cb.onUnCheckRow && _cb.onUnCheckRow(r, i, rowEl, api);// 取消选中回调
                    }
                }
            }
        };
        // 单选情况下选中，先将所有行取消选中
        if(bSingle && chkStats == true){
            var idxs = fGetCheckedIdx();
            for(i = 0; i < idxs.length; i++){
                applyRow(_data[idxs[i]], false);
            }
        }
        if(Object.prototype.toString.call(row) === '[object Array]'){// 修改数组中的行的选中状态
            if(bSingle && chkStats == true){// 单选情况下选中，只选中第一项
                applyRow(row[0], chkStats);
            }
            else{
                for(i = 0; i < row.length; i++){
                    applyRow(row[i], chkStats);
                }
            }
        }
        else{
            applyRow(row, chkStats);
        }
    };
    // 获取行绘制字符串。curdata 当前行数据，i 在当前页的索引，pi 分页数据中的第几条
    var fGetTrStr = function(curdata, i, pi){
        var str = '',
            rowCls = '';// 行样式字符串
        if(opts.rowClser){
            rowCls = opts.rowClser(curdata, i) || '';
        }
        if(sChkField && curdata[sChkField]){// 当前行选中
            rowCls += ' trchk';
        }
        str += '<tr data-i="' + i + '" class="' + rowCls + '">';
        for(var j = 0, col, aCls; j < aColsDef.length; j++){
            col = aColsDef[j];
            aCls = [];
            if(col.cls){
                aCls.push(col.cls);
            }
            if(col.align){// 对齐方式
                aCls.push(col.align);
            }
            if(j == 0 && col.type == 'no'){// 序号在第一列
                aCls.push('ctrltd');
            }
            str += '<td data-name="' + col.name + '"' +
                (aCls.length ? ' class="' + aCls.join(' ') + '"' : '') +
                (col.showTip ? ' title="' + (curdata[col.field] == undefined ? '' : curdata[col.field]) + '"' : '') + '>';
            if(col.type == 'no'){
                str += pi;
            }
            else if(col.type == 'chk'){
                str += '<input class="listtable-chkipt" type="checkbox"' + (curdata[col.field] ? ' checked' : '') + ' />';
            }
            else{
                str += '<div class="listtable-item">' +
                    (col.render ? col.render(curdata[col.field], curdata, i, api) : (curdata[col.field] == undefined ? '' : $.encodeHtml(curdata[col.field]))) +
                    '</div>';
            }
            str += '</td>';
        }
        str += '</tr>';
        return str;
    };
    // 根据请求获取的数据绘制表格内容
    // {object} datalist 数据列表
    var fSetCurData = function(datalist){
        var startNo = 1,
            dataStr = '';
        if(_pageObj){
            startNo = (_pageObj.curpage - 1) * _pageObj.percount + 1 || 1;// 根据分页对象获得第一条的序号
        }
        _curData = datalist;
        if(datalist && datalist.length > 0){
            // 绘制当前页数据
            for(var i = 0; i < datalist.length; i++){
                dataStr += fGetTrStr(datalist[i], i, startNo + i);
            }
        }
        else if(opts.prompt != false){
            dataStr = '<tr><td class="nodata" colspan="' + aColsDef.length + '">未查询到符合条件的数据!</td></tr>'
        }
        el.find('tbody').html(dataStr);
        el.find('thead>tr>[data-name="' + sChkName + '"] input').prop('checked', false);// 避免全部删除后仍选中 TODO 应根据选中项判断
        _cb && _cb.afterShowData && _cb.afterShowData(api);
    };
    // 获取当前列表所有选中行索引集合
    var fGetCheckedIdx = function(){
        var sels = [];
        if(sChkField && _data){
            for(var i = 0; i < _data.length; i++){
                if(_data[i][sChkField]){
                    sels.push(i);
                }
            }
        }
        return sels;
    };
    // 获取当前所有选中行的数据列表
    var fGetCheckedRows = function(){
        var chks = fGetCheckedIdx();
        var rows = [];
        for(var i = 0; i < chks.length; i++){
            rows.push(_data[chks[i]]);
        }
        return rows;
    };
    /**
     * 合并指定单元格
     * @param merges {Array} 要合并的单元格信息
     *   merges[i].rowI {int} 要合并的单元格在当前页的索引
     *   merges[i].name {string} 要合并的单元格所在列对应的 name
     *   merges[i].rowspan {int} 合并几行
     *   merges[i].colspan {int} 合并几列
     */
    var fMerge = function(merges){
        if(!merges || !merges.length) return;
        var _adjustMerge = function(mergeItem){
            var rowI = mergeItem['rowI'], colI = -1
                , name = mergeItem['name']
                , rowspan = mergeItem['rowspan']
                , colspan = mergeItem['colspan'];
            var cellEl = el.find('tbody>tr[data-i="' + rowI + '"]>td[data-name="' + name + '"]')
                , rowEl;
            if(!cellEl.length) return;
            for(var i = 0, len = aColsDef.length; i < len; i++){// 获取当前列定义索引，由于判断过 cellEl，所以 colI 必有意义
                if(name == aColsDef[i]['name']){
                    colI = i;
                    break;
                }
            }
            rowspan && cellEl.attr('rowspan', rowspan);
            colspan && cellEl.attr('colspan', colspan);
            /* 调整受影响的单元格 */
            rowspan = parseInt(cellEl.attr('rowspan'), 10) || 1;
            colspan = parseInt(cellEl.attr('colspan'), 10) || 1;
            for(var j = 0; j < rowspan; j++){
                rowEl = el.find('tbody>tr[data-i="' + (rowI + j) + '"]');
                for(var k = 0; k < colspan; k++){
                    if(j == 0 && k == 0) continue;// 保留当前单元格
                    rowEl.find('>td[data-name="' + aColsDef[colI + k]['name'] + '"]').remove();
                }
            }
        };
        for(var i = 0, len = merges.length; i < len; i++){
            _adjustMerge(merges[i]);
        }
    };
    // 尺寸重算，仅支持动态尺寸表格
    var fResetSize = function(){
        if(!opts.staticSize){// 动态尺寸表格需要通过 resizeListener 重算尺寸
            $.resizeListener.fResetSize(el);
        }
    };

    // 获取当前列表配置 opts（为扩展编辑表格添加此接口）
    var fGetOpts = function(){
        return opts;
    };
    // 返回整个表格的的数据列表，后台分页且未过滤时，同 fGetCurData() 的结果
    var fGetData = function(){
        return $.extend([], _data);// 避免用户直接操作返回的数组，导致 _data 发生变化，但要注意的是，数组中的每一项并非副本，修改也将污染组件数据
    };
    // 返回当前页的数据列表
    var fGetCurData = function(){
        return $.extend([], _curData);// 避免用户直接操作返回的数组，导致 _data 发生变化，但要注意的是，数组中的每一项并非副本，修改也将污染组件数据
    };
    // 返回当前的分页信息
    var fGetPage = function(){
        return _pageObj;
    };
    // 返回当前分页接口 api
    var fGetPageApi = function(){
        return oPagebarApi;
    };

    // 请求数据（分页）并绘制到 DOM
    var fBindData = function(){
        var jqManu, oNewPageOpts,// 分页元素，分页配置
            optsData = opts.data;// 配置的前台数据源
        // 数据绑定前先清空原有的数据源
        _data = null;
        _curData = null;
        oPagebarApi && oPagebarApi.destroy();// 销毁分页对象
        oPagebarApi = null;
        _pageObj = null;
        if(!optsData && !opts.url) return;// 没有定义数据源时不进行数据部分绘制，用于支持初始化时不带入数据
        // 记录全部数据，bFilter = true 时允许将数据同步到过滤组件
        var fSetData = function(newData, bFilter){
            _data = newData;
            if(bFilter && oFilterApi){
                oFilterApi.setData(_data);// 关键字清空，不触发过滤回调
            }
        };
        if(oPageOpts){
            jqManu = el.next('.manu');
            if(!jqManu.length){
                jqManu = $('<div class="manu"></div>');
                el.after(jqManu);
            }
            oNewPageOpts = {};
            // 前台分页
            if(optsData){
                fSetData(optsData, true);// 设置 _data，支持过滤
                $.extend(oNewPageOpts, oPageOpts, {
                    data: optsData,
                    afterFlip: function(datalist, pageObj, data){
                        _pageObj = pageObj;
                        fSetCurData(datalist);
                    }
                });
            }
            // 后台分页
            else{
                $.extend(oNewPageOpts, oPageOpts, {
                    url: opts.url,// 后台分页时请求的url
                    args: opts.args,
                    showloading: opts.showloading,
                    afterFlip: function(datalist, pageObj, data){
                        fSetData(datalist, false);// 设置 _data，不支持过滤
                        _pageObj = pageObj;
                        fSetCurData(datalist);
                    }
                });
            }
            oPagebarApi = jqManu.initPageBar(oNewPageOpts);// 分页组件初始化
            delete oPageOpts.autoPage;// 数据完成重绘，autoPage 还原
        }
        else{
            // 前台不分页
            if(optsData){
                fSetData(optsData, true);// 设置 _data，支持过滤
                fSetCurData(optsData);
            }
            // 后台不分页
            else{
                $.request({
                    onSuccess: function(data){
                        fSetData(data, true);// 设置 _data，支持过滤
                        fSetCurData(data);
                    },
                    showloading: opts.showloading,
                    ajaxOpts: {
                        url: opts.url,
                        data: opts.args
                    }
                });
            }
        }
    };

    // 过滤组件回调事件
    var fAfterFilter = function(newData, keyword){
        if(oPageOpts){// 分页
            if(opts.data){// 前台
                oPagebarApi && oPagebarApi.fReset({
                    data: newData// 仅会改变 _curData
                });
            }
        }
        else{// 不分页 - 前台/后台
            fSetCurData(newData);// 仅会改变 _curData
        }
    };
    // 初始化或重置过滤组件
    var fInitFilter = function(){
        var filter, filterOpts;
        if(opts.filterEl){
            // 后台分页时不支持过滤
            filter = opts.filter;
            filter.hide = opts.singleChk && filter.hide != false;// 只有单选且允许隐藏时为 true，多选禁止隐藏
            filterOpts = {
                fields: filter.fields,
                afterFilter: fAfterFilter
            };
            if(filter.pySupport != undefined){
                filterOpts.pySupport = filter.pySupport;
            }
            if(filter.placeholder !== undefined){
                filterOpts.placeholder = filter.placeholder;
            }
            // 初始化或重置过滤组件
            if(oFilterApi){
                oFilterApi.fReset(filterOpts);
            }
            else{
                oFilterApi = opts.filterEl.initFilter(filterOpts);
            }
        }
    };

    // 绑定行事件
    var fBindRowEvent = function(){
        var eType;// 临时变量
        // 行事件
        var cbRow = _cb.row;
        if(typeof cbRow == 'object'){
            var cbRowHandle = function(handle){
                return function(e){
                    var el = $(this),
                        i = parseInt(el.attr('data-i'), 10);
                    handle.call(this, e, _curData[i], i, api);
                };
            };
            for(eType in cbRow){
                if(cbRow.hasOwnProperty(eType))
                    el.off(eType + '.row')// TODO del 兼容旧版本的写法，不合逻辑（错误地对同一个 el 初始化时，可能会导致不需要触发的行事件因为无法解绑导致触发）
                      .on(eType + '.row', 'tbody>tr', cbRowHandle(cbRow[eType]));
            }
        }
    };
    // 解绑行事件
    var fUnbindRowEvent = function(){
        var eType;// 临时变量
        // 行事件
        var cbRow = _cb.row;
        if(typeof cbRow == 'object'){
            for(eType in cbRow){
                if(cbRow.hasOwnProperty(eType))
                    el.off(eType + '.row');
            }
        }
    };
    // 绑定单元格事件
    var fBindColEvent = function(){
        var name, eType;// 临时变量
        // 单元格事件
        var cbCell = _cb.cell;
        if(typeof cbCell == 'object'){
            var cbCellHandle = function(handle, name){
                var col = fFindCol('name', name)[0];
                return function(e){
                    var el = $(this)
                        , i = parseInt(el.parent().attr('data-i'), 10)
                        , row = _curData[i];
                    handle.call(this, e, row ? row[col.field] : undefined, row, i, api);
                };
            };
            for(name in cbCell){
                if(cbCell.hasOwnProperty(name))
                    for(eType in cbCell[name]){
                        if(cbCell[name].hasOwnProperty(eType)){
                            el.off(eType + '.cell_' + name)// TODO del 兼容旧版本的写法，不合逻辑（错误地对同一个 el 初始化时，可能会导致重名但不需要绑定事件的列上事件无法被解绑，导致错误触发）
                              .on(eType + '.cell_' + name, 'tbody>*>td[data-name="' + name + '"]', cbCellHandle(cbCell[name][eType], name));
                        }
                    }
            }
        }
    };
    // 解绑单元格事件
    var fUnbindColEvent = function(){
        var name, eType;// 临时变量
        var cbCell = _cb.cell;
        if(typeof cbCell == 'object'){
            for(name in cbCell){
                if(cbCell.hasOwnProperty(name))
                    for(eType in cbCell[name]){
                        if(cbCell[name].hasOwnProperty(eType)){
                            el.off(eType + '.cell_' + name);
                        }
                    }
            }
        }
    };

    // 事件绑定
    var fBindEvent = function(){
        _cb = opts.callback;
        if(_cb){
            fBindRowEvent();// 行事件
            fBindColEvent();// 单元格事件
        }
    };
    // 事件解绑
    var fUnbindEvent = function(){
        if(_cb){
            fUnbindRowEvent();
            fUnbindColEvent();
        }
    };
    // 绑定选中事件 - 必须在 cols 定义完成之后
    var fBindCheckEvent = function(){
        var bShowChk = !!fFindCol('type', 'chk').length;// 是否显示选择列
        // 先解绑
        el.off('click._row')
          .off('click.chk')
          .off('click.chkAll');
        // 不可选择或存在选择列时，绑定行点击变色事件。反之，可选且不存在选择列时，点击行应执行选中行操作
        if(!sChkField || bShowChk){
            el.on('click._row', 'tbody>tr[data-i]', function(){
                var tr = $(this);
                if(tr.hasClass('now')) return;
                tr.siblings('.now').removeClass('now');
                tr.addClass('now');
            });
        }
        // 允许选中，绑定行选中事件
        if(sChkField){
            if(bShowChk){// 显示选择列 - 无论单选/多选，事件添加在选择列单元格
                el.on('click.chk', 'tbody>tr>td[data-name="' + sChkName + '"]', function(){
                    var td = $(this),
                        rowI = parseInt(td.parent().attr('data-i'), 10),
                        stat = _curData[rowI][sChkField];
                    fCheckRow(_curData[rowI], !stat);
                });
                if(opts.singleChk === false){// 复选，绑定全选/全不选事件
                    el.on('click.chkAll', 'thead>tr>[data-name="' + sChkName + '"]', function(e){
                        var stat, input;
                        input = $(this).children('input');
                        if(e.target.tagName.toLocaleLowerCase() == 'input'){// 事件源是 input
                            stat = input.prop('checked');
                        }
                        else{
                            stat = !input.prop('checked');
                        }
                        input.prop('checked', stat);
                        fCheckRow(_curData, stat);
                    });
                }
            }
            else{// 不显示选择列，一定是单选，点击行选中
                el.on('click.chk', 'tbody>tr[data-i]', function(){
                    var tr = $(this),
                        rowI = parseInt(tr.attr('data-i'), 10),
                        stat = _curData[rowI][sChkField];
                    fCheckRow(_curData[rowI], true);// 选中点击行
                });
            }
        }
    };

    // 需要动态计算尺寸时的尺寸处理函数 - 根据当前容器宽度，计算每个 <col /> 的实际像素，并添加样式
    var fCaculateDyn = function(bInit){
        var nOutWidth = nElWidth - opts.tableHBorderW;// table 中 td 的总尺寸，减去 <table> 横向边框
        var defaultColWidth = opts.defaultColWidth,// 默认列宽,未指定列宽时最小的列宽
            avgColWidth,// 未指定列宽列的平均宽度
            allCols = 0,// 列宽（含border）总和
        //preCols = 0,// 非用户自定义列的列（序号、复选框）宽总和，若显示，总在表格最左侧
            unAsignedCols; // 组件可见区域中可分配的宽度
        var jqCols = el.find('col');
        // 处理列宽，计算已定义的列宽总和
        $.each(aColsDef, function(index, col){
            // 初始化，将用户定义的列宽存入 aInitColsW，将未设置列宽的列索引存入 aAdaptCols 以便之后的计算
            if(bInit){
                var initColW = col.width;
                if(typeof initColW == 'number'){// 数字
                    aInitColsW.push(initColW);
                }
                else if(typeof initColW == 'string'){
                    if(/^\s*\d+\.?\d*%\s*$/.test(initColW)){// 合法的 % 数
                        aInitColsW.push(initColW);
                    }
                    else if(/^\s*\d+\.?\d*(?:px)?\s*$/.test(initColW)){// 合法 number 像素值
                        initColW = parseFloat(initColW);
                        aInitColsW.push(initColW);
                    }
                    else{// 标记为未定义列宽
                        initColW = undefined;
                        aInitColsW.push(initColW);
                        aAdaptCols.push(index);
                    }
                }
                else{// 标记为未定义列宽
                    initColW = undefined;
                    aInitColsW.push(initColW);
                    aAdaptCols.push(index);
                }
            }
            // 为用户初始化定义列宽的列确定宽度 - 全部转换为数字
            var colW = aInitColsW[index];
            if(typeof colW == 'string'){// 已定义的 % 列
                colW = Math.round(nOutWidth * parseFloat(colW.replace('%', '')) / 100);
            }
            if(typeof colW != 'undefined'){// 已定义的列
                col.width = colW;
                allCols += colW;// 累加到列宽总和

                jqCols.eq(index).css('width', colW + 'px');// 绘制到 dom
            }
        });
        //scope.multiselect && (preCols += dgConf.leftColWidth);// 允许多选
        //scope.showno && (preCols += dgConf.leftColWidth);// 显示序号
        //allCols += preCols;

        //剩余列宽总和 = datagrid 内容部分总宽度 - 纵向滚动条（始终存在）- 已经给定的列宽总和
        unAsignedCols = nOutWidth - allCols;
        avgColWidth = Math.floor(unAsignedCols / aAdaptCols.length);
        if(avgColWidth < defaultColWidth){ //如果计算出出平均列宽小于默认列宽，直接定义为默认列宽
            $.each(aAdaptCols, function(m, index){
                aColsDef[index].width = defaultColWidth;
                allCols += defaultColWidth;
                unAsignedCols -= defaultColWidth;
                jqCols.eq(index).css('width', defaultColWidth + 'px');// 绘制到 dom
            });
        }
        else{
            $.each(aAdaptCols, function(m, index){
                if(m == aAdaptCols.length - 1){
                    aColsDef[index].width = unAsignedCols;
                    allCols += unAsignedCols;
                    unAsignedCols = 0;
                }
                else{
                    aColsDef[index].width = avgColWidth;
                    allCols += avgColWidth;
                    unAsignedCols -= avgColWidth;
                }
                jqCols.eq(index).css('width', aColsDef[index].width + 'px');// 绘制到 dom
            });
        }
        el.find('.listtable').css('width', allCols);// 修改 table 宽度
    };
    // 不需动态计算尺寸时的尺寸处理函数 - 直接根据列宽定义为 <col /> 添加样式
    var fCaculateStat = function(bInit){
        var jqCols = el.find('col');
        $.each(aColsDef, function(index, col){
            var initColW = col.width || '';
            jqCols.eq(index).css('width', initColW);// 绘制到 dom
        });
        el.find('.listtable').css('width', '100%');// 修改 table 宽度
    };

    // 实际列宽计算 - 未定义列宽的列，根据剩余尺寸计算平均值，若平均值 < opts.defaultColWidth，则以 opts.defaultColWidth 显示
    var fComputeColsW = function(bInit){
        var fCaculate = opts.staticSize ? fCaculateStat : fCaculateDyn;
        // 将 bInit 规范为布尔值，$.resizeListener 中调用时，不排除以后需要传递 event 对象，因此保留这一步
        bInit = bInit === true;
        if(bInit){
            // 初始化表格内容部分宽度和高度
            nElWidth = el.innerWidth();
            // 计算列宽等
            fCaculate(bInit);
        }
        else{
            var curWidth = el.innerWidth();
            if(nElWidth !== curWidth){// 容器宽度发生变化，需要重新计算列宽
                nElWidth = curWidth;
                fCaculate(bInit);
            }
        }
    };

    // 重置列定义
    var fResetColsDef = function(){
        /* 绘制基础 dom，确定表头，清空数据部分 */
        var colAlignArr = ['center', 'left', 'right'];// col.align 合法值
        var jqTable = el.children('.listtable');
        var tableStr = '<table class="listtable" cellspacing="0" cellpadding="0">',
            sCols = '',
            sThs = '';
        var bShowHead = opts.showHead,
            bShowNo, bShowChk;// 是否定义了序号列、选择框列
        var i, oStr, len, curCol;// 临时变量，遍历用
        var aPreCol = [];
        var sDefaultChkField = typeof opts.chkField == 'string' ? opts.chkField : '__checked';
        var fGetColStrObj = function(col){
            var sCol = '',
                sTh = '';
            var sName = col.name,
                sTitle = col.title,
                sThIn;
            if(!sName){// name 未定义时，与 field 相同
                sName = col.name = col.field;
            }
            // 序号列，只允许有一列
            if(col.type == 'no'){
                if(bShowNo){
                    delete col.type;// 改为普通列
                }
                else{
                    bShowNo = true;
                    if(!sName){
                        sName = col.name = '__no';// 序号列应根据数据计算，不需要有 field
                    }
                    sNoName = sName;
                    if(typeof sTitle == 'undefined'){
                        sTitle = col.title = '序号';
                    }
                }
            }
            // 选择框列，只允许有一列
            if(col.type == 'chk'){
                if(bShowChk){
                    delete col.type;// 改为普通列
                }
                else{
                    bShowChk = true;
                    if(!col.field){// 未定义时，设为默认字段
                        col.field = sDefaultChkField
                    }
                    sChkField = col.field;// cols 中定义的 field 优先级最高
                    if(!sName){
                        sName = col.name = sChkField;
                    }
                    sChkName = sName;
                    // 全选/全不选有关联事件，因此选择列暂不支持自定义表头
                    sTitle = col.title = opts.singleChk ? '' : '<input class="listtable-chkipt" type="checkbox" />';
                    delete col.hrender;
                }
            }
            // 水平对齐方式
            if(!col.align || colAlignArr.indexOf(col.align) == -1){// 未指定合法的 align
                if(col.type == 'number'){// 未指定 align，且为数字，默认居右
                    col.align = 'right';
                }
                else if(col.type == 'no' || col.type == 'chk'){// 序号或复选框列，默认居中
                    col.align = 'center';
                }
                else{// 移除不合法的 align
                    delete col.align;
                }
            }
            // 拼接字符串
            sCol += '<col data-name="' + col.name + '" />';
            if(bShowHead){// 显示 thead
                sThIn = col.hrender ? col.hrender(sName, sTitle) : sTitle;
                sTh += '<th data-name="' + sName + '">' + (sThIn || '&nbsp;') + '</th>';
            }
            return {
                col: sCol,
                th: sTh
            }
        };

        // fReset 可能修改列配置，先重置相关全局变量
        aColsDef = opts.cols;
        aInitColsW.length = 0;
        aAdaptCols.length = 0;
        sNoName = sChkName = sChkField = undefined;
        // 先处理 opts.cols 定义的列
        for(i = 0, len = aColsDef.length; i < len; i++){
            oStr = fGetColStrObj(aColsDef[i]);
            sCols += oStr.col;
            sThs += oStr.th;
        }
        /* 兼容代码 start TODO del 兼容旧版本的写法，opts.cols 处理完成后，判断是否有旧版本的序号列、选择列 */
        if(!bShowNo && opts.showno){// 定义了序号列
            aPreCol.push({type: 'no', width: '50px'});// 只在兼容时默认宽度，cols 中自定义时可能需要自适应列宽
        }
        if(!bShowChk && !opts.singleChk && opts.chkField){// opts.cols 未定义选择列，复选且定义了 opts.chkField
            aPreCol.push({type: 'chk', width: '30px'});
        }
        // 处理左侧列，倒序往前添加
        for(i = aPreCol.length - 1; i >= 0; i--){
            curCol = aPreCol[i];
            oStr = fGetColStrObj(curCol);
            sCols = oStr.col + sCols;
            sThs = oStr.th + sThs;
            aColsDef.unshift(curCol);// 添加到列定义
        }
        /* 兼容代码 end */
        // 未显示选中列时，处理为单选（点击行选中）
        if(!bShowChk){
            sChkField = sDefaultChkField;// 记录字段，单选不需记录 sChkName
            opts.singleChk = true;// 2017.11.21 新增，不显示选择列时，强制设为单选
        }
        // 拼接字符串
        tableStr += '<colgroup>' + sCols + '</colgroup>';
        if(bShowHead){
            tableStr += '<thead><tr>' + sThs + '</tr></thead>';
        }
        tableStr += '<tbody></tbody>' +
            '</table>';
        // 绘制 DOM
        if(jqTable.length){
            jqTable.replaceWith(tableStr);
        }
        else{
            el.append(tableStr);
        }
        /* 重计算列宽 */
        fComputeColsW(true);
    };
    // 修改初始化时的配置，重新获取数据
    var fReset = function(cusOpts){// fInit 中调用时传入 true
        var oCusPage;
        if(typeof cusOpts == 'object'){
            // 处理分页配置
            oCusPage = cusOpts.page;
            if(oCusPage === false){
                opts.page = false;
            }
            else if(oCusPage){
                opts.page = $.extend({}, oPageOpts, oCusPage);
            }
            delete cusOpts.page;// 移除 page，避免扩展时将原有的 opts.page 直接覆盖
            // 扩展配置
            $.extend(opts, cusOpts);
            // 重定义列，需要重新初始化
            if(cusOpts.cols){
                fResetColsDef();// 重置列定义
            }
            // 绑定事件
            if(cusOpts.callback){
                // 只有 callback.cell 和 callback.row 需要重新绑定
                // callback.onCheckRow, callback.onUnCheckRow, callback.afterShowData 调用时会自动使用最新事件
                fUnbindEvent();// 解绑原先的事件
                fBindEvent();
            }
            // 重置过滤组件
            if(cusOpts.filter){
                fInitFilter();
            }
            // 数据配置
            if(cusOpts.data){// 重设了 data，将 url 置空
                opts.url = undefined;
            }
            else if(cusOpts.url){// 重设了 url，将 data 置空
                opts.data = undefined;
            }
        }
        else if(cusOpts === true){// 初始化时，处理 opts
            fResetColsDef();// 重置列定义
            fBindEvent();// 事件绑定
            fInitFilter();
            // 数据配置
            if(opts.data){
                opts.url = undefined;
            }
        }
        /* 应用配置 */
        fBindCheckEvent();// 由于 cols 等配置变化可能导致选中触发条件改变，需要重绑选中事件
        // 尺寸变化事件
        if(opts.staticSize){
            $.resizeListener.fUnBind(el, fComputeColsW);// 解绑容器尺寸变化事件
        }
        else{
            $.resizeListener.fBind(el, fComputeColsW);// 绑定容器尺寸变化事件
        }
        // 分页配置
        oPageOpts = opts.page;
        // 配置完成，获取数据
        fBindData();
    };

    var fInit = function(){
        el.addClass('listtableDiv');
        fReset(true);
    };
    api = {
        el: el,// 获取当前 grid 对应的 el
        fResetSize: fResetSize,// 初始化时可能由于元素隐藏或祖先元素隐藏，导致无法在容器变化时重算尺寸，此时可手动调用此方法触发进行尺寸重算
        fReset: fReset,// 扩展初始化时的配置，重新请求数据（分页）
        resetData: fReset,// TODO del 兼容旧版本，现改为 fReset()
        getData: fGetData,// 返回整个表格的的数据列表 {Array}，后台分页且未过滤时，同 obj.getCurData() 的结果
        getCurData: fGetCurData,// 返回当前显示（分页时为当前页数据，过滤时为过滤后当前页数据）的数据列表 {Array}
        setCurData: fSetCurData,// 重置当前显示的数据列表，不会影响 obj.getData() 的结果
        setData: fSetCurData,// TODO del 兼容旧版本，现改为 setCurData()
        findCol: fFindCol,// 根据条件查找所有符合的 cols[i].key=val 的列定义对象集合，未找到则为 []
        findRow: fFindRow,// 根据条件查找所有符合的 data[i].field=val 的行对象集合，未找到则为 []
        getCheckedIdx: fGetCheckedIdx,// 获取当前列表所有选中行索引，返回选中行索引数组，未找到则为 []
        getCheckedRows: fGetCheckedRows,// 获取当前列表所有选中行，返回选中行数据数组，未找到则为 []
        checkRow: fCheckRow,// 修改指定行的选中状态
        getPage: fGetPage,// 返回当前的分页信息，返回值 {percount,totalpage,curpage,totalcount}
        getPageApi: fGetPageApi,// 返回当前分页接口 api
        getPageObj: fGetPageApi,// TODO del 兼容旧版本，现改为 getPageApi()
        merge: fMerge,// 合并单元格
        getOpts: fGetOpts// 获取当前列表配置（为扩展编辑表格等功能添加此接口）
    };
    fInit();
    return api;
};